﻿#region Copyright (c) 2012-10, Olaf Kober <amarok.projects@gmail.com>
//================================================================================
//	Permission is hereby granted, free of charge, to any person obtaining a copy
//	of this software and associated documentation files (the "Software"), to deal
//	in the Software without restriction, including without limitation the rights
//	to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
//	copies of the Software, and to permit persons to whom the Software is
//	furnished to do so, subject to the following conditions:
//
//	The above copyright notice and this permission notice shall be included in
//	all copies or substantial portions of the Software.
//
//	THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
//	IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
//	FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
//	AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
//	LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
//	OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
//	THE SOFTWARE.
//================================================================================
#endregion

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Linq.Expressions;
using System.Reflection;
using System.Text;
using System.Threading;
using Amarok.Native;
using Amarok.Resources;


namespace Amarok
{
	/// <summary>
	/// This utility type contains static methods for various common tasks.
	/// </summary>
	public static class Helper
	{
		#region ++ Public Interface (Constants) ++

		/// <summary>
		/// Returns the constant 1024 which represents 1 KB (kilobyte)
		/// </summary>
		public const Int32 KiloByte = 1024;

		/// <summary>
		/// Returns the constant 1048576 which represents 1 MB (megabyte)
		/// </summary>
		public const Int32 MegaByte = 1024 * KiloByte;

		/// <summary>
		/// Returns the constant 1073741824 which represents 1 GB (gigabyte)
		/// </summary>
		public const Int32 GigaByte = 1024 * MegaByte;

		/// <summary>
		/// Returns the constant 1099511627776 which represents 1 TB (terabyte)
		/// </summary>
		public const Int64 TeraByte = (Int64)1024 * GigaByte;

		#endregion

		#region ++ Public Interface (Dispose) ++

		/// <summary>
		/// Safely disposes the supplied object.
		/// </summary>
		/// 
		/// <param name="disposableObject">
		/// A reference to a disposable object. Null references are ignored.</param>
		public static void Dispose(IDisposable disposableObject)
		{
			if (disposableObject == null)
				return;

			disposableObject.Dispose();
		}

		#endregion

		#region ++ Public Interface (EmptyBytes) ++

		// constants
		private static readonly Byte[] sEmptyBytesArray = new Byte[0];


		/// <summary>
		/// Gets a zero-length array of bytes.
		/// </summary>
		public static Byte[] EmptyBytes
		{
			get
			{
				return sEmptyBytesArray;
			}
		}

		#endregion

		#region ++ Public Interface (EmptyChars) ++

		// constants
		private static readonly Char[] sEmptyCharsArray = new Char[0];


		/// <summary>
		/// Gets a zero-length array of Chars.
		/// </summary>
		public static Char[] EmptyChars
		{
			get
			{
				return sEmptyCharsArray;
			}
		}

		#endregion

		#region ++ Public Interface (EmptyStrings) ++

		// constants
		private static readonly String[] sEmptyStringsArray = new String[0];


		/// <summary>
		/// Gets a zero-length array of Strings.
		/// </summary>
		public static String[] EmptyStrings
		{
			get
			{
				return sEmptyStringsArray;
			}
		}

		#endregion

		#region ++ Public Interface (ExpandText) ++

		/// <summary>
		/// Expands placeholders in the given text string with values returned from the specified lookup function. 
		/// Placeholders can be specified as $(key) where 'key' will be supplied to the lookup function to obtain 
		/// the placeholder's replacement value.
		/// </summary>
		/// 
		/// <param name="text">
		/// The text in which placeholders should be expanded. Null or empty strings are allowed.</param>
		/// <param name="lookupFunc">
		/// A lookup function that returns a replacement value for the given key.</param>
		/// 
		/// <returns>
		/// The new text string with expanded placeholders.</returns>
		/// 
		/// <exception cref="ArgumentNullException">
		/// A null reference was passed to a method that did not accept it as a valid argument.</exception>
		public unsafe static String ExpandText(String text, Func<String, String> lookupFunc)
		{
			Verify.NotNull(lookupFunc, "lookupFunc");

			if (String.IsNullOrEmpty(text))
				return text;
			if (text.IndexOf('$') == -1)
				return text;

			StringBuilder result = new StringBuilder(text.Length * 2);

			Boolean foundDollar = false;
			Boolean inExpansion = false;
			Int32 beginIndex = -1;
			Int32 endIndex = -1;
			Int32 length = text.Length;

			fixed (Char* ptrSrc = text)
			{
				for (Int32 i = 0; i < length; i++)
				{
					Char sourceChar = ptrSrc[i];

					if (inExpansion)
					{
						if (sourceChar == ')')
						{
							// found end of expansion
							inExpansion = false;
							endIndex = i;

							var key = text.Substring(beginIndex + 1, endIndex - beginIndex - 1);
							var value = lookupFunc(key);
							result.Append(value);
							continue;
						}
						else
						{
							continue;
						}
					}
					else
					{
						if (sourceChar == '$')
						{
							if (foundDollar)
							{
								// found escaped dollar
								foundDollar = false;
								result.Append('$');
								continue;
							}
							else
							{
								// found first dollar
								foundDollar = true;
								continue;
							}
						}
						else if (sourceChar == '(')
						{
							if (foundDollar)
							{
								// found begin of an expansion
								foundDollar = false;
								inExpansion = true;
								beginIndex = i;
								continue;
							}
						}
					}

					foundDollar = false;
					result.Append(sourceChar);
				}
			}

			return result.ToString();
		}

		#endregion

		#region ++ Public Interface (FindBaseException) ++

		/// <summary>
		/// Returns the supplied exception or one of its inner exceptions matching the specified exception type. 
		/// This method does support AggregateException and will iterate through all inner exceptions and all
		/// inner AggregateExceptions.
		/// 
		/// This method is useful for finding a specific exception type that is saved as one of the inner exceptions, 
		/// without relying onto whether it's the first, second or n-th inner exception.
		/// </summary>
		/// 
		/// <typeparam name="T">
		/// The type of exception the method should return.</typeparam>
		/// 
		/// <param name="exception">
		/// The outer-most exception whose inner exceptions are searched. If the supplied exception itself matches 
		/// the requested exception type, then it is returned. A null value is valid.</param>
		/// 
		/// <returns>
		/// A reference to the first exception that matches the specified exception type, if the supplied exception 
		/// or one of its inner exceptions matches; otherwise null.
		/// </returns>
		public static T FindBaseException<T>(Exception exception)
			where T : Exception
		{
			while (exception != null)
			{
				var typedException = exception as T;

				if (typedException != null)
				{
					return typedException;
				}

				var aggregateException = exception as AggregateException;

				if (aggregateException != null)
				{
					foreach (var innerException in aggregateException.InnerExceptions)
					{
						var result = FindBaseException<T>(innerException);

						if (result != null)
							return result;
					}
				}

				exception = exception.InnerException;
			}

			return null;
		}

		#endregion

		#region ++ Public Interface (FindBaseExceptions) ++

		/// <summary>
		/// Searches through all inner exceptions and returns a collection of exceptions that match the given 
		/// exception type. This method does support AggregateException and will iterate through all inner exceptions 
		/// and all inner AggregateExceptions.
		/// </summary>
		/// 
		/// <typeparam name="T">
		/// The type of exception the method should return.</typeparam>
		/// 
		/// <param name="exception">
		/// The outer-most exception whose inner exceptions are searched. If the supplied exception itself matches 
		/// the requested exception type, then it is returned too. A null value is valid.</param>
		/// 
		/// <returns>
		/// A collection of exceptions that match the specified exception type, if the supplied exception or at 
		/// least one of its inner exceptions matches; otherwise an empty collection is returned.
		/// </returns>
		public static T[] FindBaseExceptions<T>(Exception exception)
			where T : Exception
		{
			var results = new List<T>();

			while (exception != null)
			{
				var typedException = exception as T;

				if (typedException != null)
				{
					results.Add(typedException);
				}

				var aggregateException = exception as AggregateException;

				if (aggregateException != null)
				{
					foreach (var innerException in aggregateException.InnerExceptions)
					{
						var result = FindBaseExceptions<T>(innerException);

						if (result != null)
							results.AddRange(result);
					}

					return results.ToArray();
				}

				exception = exception.InnerException;
			}

			return results.ToArray();
		}

		#endregion

		#region ++ Public Interface (FormatBuffer) ++

		/// <summary>
		/// Formats the supplied buffer to a string consisting of hex-formatted byte values separated by semicolons.
		/// </summary>
		/// 
		/// <param name="buffer">
		/// The byte buffer to format.</param>
		/// 
		/// <returns>
		/// A string containing the formatted byte buffer.</returns>
		/// 
		/// <exception cref="ArgumentNullException">
		/// A null reference was passed to a method that did not accept it as a valid argument.</exception>
		public static String FormatBuffer(Byte[] buffer)
		{
			Verify.NotNull(buffer, "buffer");
			return FormatBuffer(buffer, 0, buffer.Length);
		}

		/// <summary>
		/// Formats the supplied buffer segment to a string consisting of hex-formatted byte values separated 
		/// by semicolons.
		/// </summary>
		/// 
		/// <param name="data">
		/// The buffer segment to format.</param>
		/// 
		/// <returns>
		/// A string containing the formatted buffer segment.</returns>
		/// 
		/// <exception cref="ArgumentOutOfRangeException">
		/// A value was passed to a method that did not accept it as a valid argument, because the value 
		/// must be positive (equal to or greater than zero).</exception>
		/// <exception cref="ArgumentOutOfRangeException">
		/// An offset value was passed to a method that did not accept it as a valid argument, because the 
		/// offset exceeds the length of the associated byte array.</exception>
		/// <exception cref="ArgumentOutOfRangeException">
		/// A count value was passed to a method that did not accept it as a valid argument, because the count 
		/// exceeds the length of the associated byte array.</exception>
		/// <exception cref="ArgumentOutOfRangeException">
		/// Offset and count values were passed to a method that did not accept it as valid arguments, because 
		/// the sum of offset and count exceeds the length of the associated byte array.</exception>
		public static String FormatBuffer(BufferSegment data)
		{
			return FormatBuffer(data.Buffer, data.Offset, data.Count);
		}

		/// <summary>
		/// Formats the supplied buffer segment to a string consisting of hex-formatted byte values separated 
		/// by semicolons.
		/// </summary>
		/// 
		/// <param name="buffer">
		/// The byte buffer to format.</param>
		/// <param name="offset">
		/// The offset in the byte buffer where the segment starts.</param>
		/// <param name="count">
		/// The length of the segment.</param>
		/// 
		/// <returns>
		/// A string containing the formatted byte buffer.</returns>
		/// 
		/// <exception cref="ArgumentNullException">
		/// A null reference was passed to a method that did not accept it as a valid argument.</exception>
		/// <exception cref="ArgumentOutOfRangeException">
		/// A value was passed to a method that did not accept it as a valid argument, because the value 
		/// must be positive (equal to or greater than zero).</exception>
		/// <exception cref="ArgumentOutOfRangeException">
		/// An offset value was passed to a method that did not accept it as a valid argument, because the 
		/// offset exceeds the length of the associated byte array.</exception>
		/// <exception cref="ArgumentOutOfRangeException">
		/// A count value was passed to a method that did not accept it as a valid argument, because the count 
		/// exceeds the length of the associated byte array.</exception>
		/// <exception cref="ArgumentOutOfRangeException">
		/// Offset and count values were passed to a method that did not accept it as valid arguments, because 
		/// the sum of offset and count exceeds the length of the associated byte array.</exception>
		public static String FormatBuffer(Byte[] buffer, Int32 offset, Int32 count)
		{
			Verify.BufferSegment(buffer, offset, count);

			var sb = new StringBuilder(count * 3);

			for (Int32 i = 0; i < count; i++)
			{
				if (sb.Length > 0)
					sb.Append(";");

				sb.Append(buffer[offset + i].ToString("X2", CultureInfo.InvariantCulture));
			}

			return sb.ToString();
		}

		#endregion

		#region ++ Public Interface (FormatException) ++

		/// <summary>
		/// Formats the supplied exception and its inner exceptions with detailed information about 
		/// each exception into a user-readable format.
		/// </summary>
		/// 
		/// <param name="exception">
		/// The exception to format. A null reference is valid.</param>
		/// 
		/// <returns>
		/// A string with exception details if an exception was supplied; otherwise null.</returns>
		public static String FormatException(Exception exception)
		{
			return ExceptionFormatter.Format(exception, CultureInfo.InvariantCulture);
		}

		/// <summary>
		/// Formats the supplied exception and its inner exceptions with detailed information about 
		/// each exception into a user-readable format.
		/// </summary>
		/// 
		/// <param name="exception">
		/// The exception to format. A null reference is valid.</param>
		/// <param name="formatProvider">
		/// An IFormatProvider that supplies culture-specific formatting information.</param>
		/// 
		/// <returns>
		/// A string with exception details if an exception was supplied; otherwise null. </returns>
		public static String FormatException(Exception exception, IFormatProvider formatProvider)
		{
			return ExceptionFormatter.Format(exception, formatProvider);
		}

		#endregion

		#region ++ Public Interface (GetApplicationPath) ++

		// cache
		private static readonly Lazy<String> sApplicationPath = new Lazy<String>(
			_GetApplicationPath, false);


		private static String _GetApplicationPath()
		{
			var type = typeof(Helper);
			var uri = new Uri(type.Assembly.CodeBase);
			return Path.GetDirectoryName(uri.LocalPath);
		}


		/// <summary>
		/// Returns the full path to the location of assembly 'amarok.common', which usually corresponds to 
		/// the application's program folder.
		/// </summary>
		/// 
		/// <remarks>
		/// <para>
		/// This method does not return the full path to the executable starting the process, instead it returns
		/// the full path to the location where assembly 'amarok.common' resides. This is an important 
		/// distinction especially in situations where the executing process is something like 'nunit.exe' that 
		/// dynamically loads assemblies from your project output folder. In that case the method will return the 
		/// full path to your project output folder and not to NUnit's installation folder.</para>
		/// </remarks>
		/// 
		/// <returns>
		/// A full path representing the application's program folder.</returns>
		public static String GetApplicationPath()
		{
			return sApplicationPath.Value;
		}

		/// <summary>
		/// Returns the full path to a path relative to the application's program folder.
		/// </summary>
		/// 
		/// <remarks>
		/// <para>
		/// The application's program folder is not the full path to the executable starting the process, but 
		/// the full path to the location where assembly 'amarok.common' resides. For more information about 
		/// this distinction refer to <see cref="Helper.GetApplicationPath()"/>.</para>
		/// </remarks>
		/// 
		/// <returns>
		/// A full path representing the specified path relative to the application's program folder.</returns>
		/// 
		/// <exception cref="ArgumentNullException">
		/// A null reference was passed to a method that did not accept it as a valid argument.</exception>
		public static String GetApplicationPath(String relativePath)
		{
			Verify.NotNull(relativePath, "relativePath");

			var path = Path.Combine(Helper.GetApplicationPath(), relativePath);
			return Path.GetFullPath(path);
		}

		#endregion

		#region ++ Public Interface (GetExecutablePath, GetExecutableName) ++

		// cache
		private static readonly Lazy<String> sExecutablePath = new Lazy<String>(
			_GetExecutablePath, false);
		private static readonly Lazy<String> sExecutableName = new Lazy<String>(
			_GetExecutableName, false);


		private static String _GetExecutablePath()
		{
			return Path.GetDirectoryName(
				NativeEnvironment.GetModuleFileName(IntPtr.Zero));
		}

		private static String _GetExecutableName()
		{
			return Path.GetFileName(
				NativeEnvironment.GetModuleFileName(IntPtr.Zero));
		}


		/// <summary>
		/// Returns the full path to the location where the executable resides that started this process.
		/// </summary>
		/// 
		/// <returns>
		/// A full path to the location where the executable resides that started this process.</returns>
		public static String GetExecutablePath()
		{
			return sExecutablePath.Value;
		}

		/// <summary>
		/// Returns the full path to a path relative to the location where the executable resides that 
		/// started this process.
		/// </summary>
		/// 
		/// <returns>
		/// A full path representing the specified path relative to the location where the executable
		/// resides that started this process.</returns>
		/// 
		/// <exception cref="ArgumentNullException">
		/// A null reference was passed to a method that did not accept it as a valid argument.</exception>
		public static String GetExecutablePath(String relativePath)
		{
			Verify.NotNull(relativePath, "relativePath");

			var path = Path.Combine(Helper.GetExecutablePath(), relativePath);
			return Path.GetFullPath(path);
		}

		/// <summary>
		/// Returns the file name of the executable that started this process.
		/// </summary>
		/// 
		/// <returns>
		/// The file name of the executable that started this process including file extensions.</returns>
		public static String GetExecutableName()
		{
			return sExecutableName.Value;
		}

		#endregion

		#region ++ Public Interface (GetInnerMostException) ++

		/// <summary>
		/// Returns the inner-most exception (root cause) of the supplied exception.
		/// </summary>
		/// 
		/// <param name="exception">
		/// The exception whose inner-most exception should be returned. Null is a valid value.</param>
		/// 
		/// <returns>
		/// A reference to the inner-most exception of the supplied exception, or null if a null reference 
		/// was supplied.</returns>
		public static Exception GetInnerMostException(Exception exception)
		{
			if (exception == null)
				return null;

			while (exception.InnerException != null)
				exception = exception.InnerException;

			return exception;
		}

		#endregion

		#region ++ Public Interface (GetPropertyName) ++

		/// <summary>
		/// Gets the property name from a property expression.
		/// </summary>
		/// 
		/// <typeparam name="T">
		/// The object type containing the property specified in the expression.</typeparam>
		/// 
		/// <param name="propertyExpression">
		/// The property expression, for example 'x => x.PropertyName'.</param>
		/// 
		/// <returns>
		/// The name of the property.</returns>
		/// 
		/// <exception cref="ArgumentNullException">
		/// A null reference was passed to a method that did not accept it as a valid argument.</exception>
		/// <exception cref="ArgumentException">
		/// The lambda expression is not a member access expression.</exception>
		/// <exception cref="ArgumentException">
		/// The lambda expression does not refer to a property.</exception>
		/// <exception cref="ArgumentException">
		/// The lambda expression does not refer to an instance member.</exception>
		public static String GetPropertyName<T>(Expression<Func<T>> propertyExpression)
		{
			Verify.NotNull(propertyExpression, "propertyExpression");

			var memberExpression = propertyExpression.Body as MemberExpression;

			if (memberExpression == null)
				throw new ArgumentException(ExceptionResources.NotAMemberAccessExpression);

			var propertyInfo = memberExpression.Member as PropertyInfo;

			if (propertyInfo == null)
				throw new ArgumentException(ExceptionResources.NotAPropertyExpression);

			var getMethodInfo = propertyInfo.GetGetMethod(true);

			if (getMethodInfo.IsStatic)
				throw new ArgumentException(ExceptionResources.NotAInstanceMemberExpression);

			return memberExpression.Member.Name;
		}

		#endregion

		#region ++ Public Interface (MakeWord, GetLowByte, GetHighByte)

		/// <summary>
		/// Combines specified low-byte and high-byte to a Word (16-bit).
		/// </summary>
		/// 
		/// <param name="highByte">
		/// The high-order byte value.</param>
		/// <param name="lowByte">
		/// The low-order byte value.</param>
		/// 
		/// <returns>
		/// The combined word value.</returns>
		public static Int32 MakeWord(Byte highByte, Byte lowByte)
		{
			return (highByte << 8) | lowByte;
		}

		/// <summary>
		/// Gets the low-order byte value from the specified Word (16-bit).
		/// </summary>
		/// 
		/// <param name="value">
		/// The word value.</param>
		/// 
		/// <returns>
		/// The low-order byte value.</returns>
		public static Byte GetLowByte(Int32 value)
		{
			return (Byte)(value & 0x00FF);
		}

		/// <summary>
		/// Gets the high-order byte value from the specified Word (16-bit).
		/// </summary>
		/// 
		/// <param name="value">
		/// The word value.</param>
		/// 
		/// <returns>
		/// The high-order byte value.</returns>
		public static Byte GetHighByte(Int32 value)
		{
			return (Byte)((value & 0xFF00) >> 8);
		}

		#endregion

		#region ++ Public Interface (MakeDWord, GetLowWord, GetHighWord)

		/// <summary>
		/// Combines specified low-word and high-word to a Double-Word (32-bit).
		/// </summary>
		/// 
		/// <param name="highWord">
		/// The high-order word value.</param>
		/// <param name="lowWord">
		/// The low-order word value.</param>
		/// 
		/// <returns>
		/// The combined double-word value.</returns>
		public static Int32 MakeDWord(Int32 highWord, Int32 lowWord)
		{
			UInt32 hw = (UInt32)(highWord & 0xFFFF);
			UInt32 lw = (UInt32)(lowWord & 0xFFFF);
			UInt32 dw = hw << 16 | lw;
			return unchecked((Int32)dw);
		}

		/// <summary>
		/// Combines the specified bytes to a Double-Word (32-bit).
		/// </summary>
		/// 
		/// <param name="uwub">
		/// The upper-word upper-byte.</param>
		/// <param name="uwlb">
		/// The upper-word lower-byte.</param>
		/// <param name="lwub">
		/// The lower-word upper-byte.</param>
		/// <param name="lwlb">
		/// The lower-word lower-byte.</param>
		/// 
		/// <returns>
		/// The combined double-word value.</returns>
		public static Int32 MakeDWord(Byte uwub, Byte uwlb, Byte lwub, Byte lwlb)
		{
			UInt32 dword = 0x00000000;
			dword |= ((UInt32)uwub << 24);
			dword |= ((UInt32)uwlb << 16);
			dword |= ((UInt32)lwub << 8);
			dword |= ((UInt32)lwlb << 0);
			return unchecked((Int32)dword);
		}

		/// <summary>
		/// Gets the low-order word value from the specified DWord (32-bit).
		/// </summary>
		/// 
		/// <param name="value">
		/// The double-word value.</param>
		/// 
		/// <returns>
		/// The low-order word value.</returns>
		public static Int32 GetLowWord(Int32 value)
		{
			return value & 0x0000FFFF;
		}

		/// <summary>
		/// Gets the high-order word value from the specified DWord (32-bit).
		/// </summary>
		/// 
		/// <param name="value">
		/// The double-word value.</param>
		/// 
		/// <returns>
		/// The high-order word value.</returns>
		public static Int32 GetHighWord(Int32 value)
		{
			return (Int32)(((UInt32)value & 0xFFFF0000) >> 16);
		}

		#endregion

		#region ++ Public Interface (GetAssemblyInformationalVersion, GetAssemblyFileVersion, ...) ++

		/// <summary>
		/// Returns the informational assembly version defined by <see cref="AssemblyInformationalVersionAttribute"/>
		/// of the specified assembly. If the supplied assembly does not define such attribute, then null is returned.
		/// </summary>
		/// 
		/// <param name="assembly">
		/// The assembly for which to return the informational assembly version.</param>
		/// 
		/// <returns>
		/// Either a string containing the informational assembly version, or null.</returns>
		/// 
		/// <exception cref="ArgumentNullException">
		/// A null reference was passed to a method that did not accept it as a valid argument.</exception>
		public static String GetAssemblyInformationalVersion(Assembly assembly)
		{
			Verify.NotNull(assembly, "assembly");

			var attribute = (AssemblyInformationalVersionAttribute)Attribute.GetCustomAttribute(
				assembly, typeof(AssemblyInformationalVersionAttribute));

			if (attribute == null)
				return null;

			return attribute.InformationalVersion;
		}

		/// <summary>
		/// Returns the assembly file version defined by <see cref="AssemblyFileVersionAttribute"/> of the 
		/// specified assembly. If the supplied assembly does not define such attribute, then null is returned.
		/// </summary>
		/// 
		/// <param name="assembly">
		/// The assembly for which to return the assembly file version.</param>
		/// 
		/// <returns>
		/// Either a string containing the assembly file version, or null.</returns>
		/// 
		/// <exception cref="ArgumentNullException">
		/// A null reference was passed to a method that did not accept it as a valid argument.</exception>
		public static String GetAssemblyFileVersion(Assembly assembly)
		{
			Verify.NotNull(assembly, "assembly");

			var attribute = (AssemblyFileVersionAttribute)Attribute.GetCustomAttribute(
				assembly, typeof(AssemblyFileVersionAttribute));

			if (attribute == null)
				return null;

			return attribute.Version;
		}

		/// <summary>
		/// Returns the assembly version defined by <see cref="AssemblyVersionAttribute"/> of the specified 
		/// assembly. If the supplied assembly does not define such attribute, then a version string of "0.0.0.0" 
		/// is returned, which represents the default assembly version for all assemblies.
		/// </summary>
		/// 
		/// <param name="assembly">
		/// The assembly for which to return the assembly version.</param>
		/// 
		/// <returns>
		/// A string containing the assembly version.</returns>
		/// 
		/// <exception cref="ArgumentNullException">
		/// A null reference was passed to a method that did not accept it as a valid argument.</exception>
		public static String GetAssemblyVersion(Assembly assembly)
		{
			Verify.NotNull(assembly, "assembly");
			return assembly.GetName().Version.ToString();
		}

		/// <summary>
		/// Returns either the informational assembly version defined by 
		/// <see cref="AssemblyInformationalVersionAttribute"/>, the assembly file version defined by
		/// <see cref="AssemblyFileVersionAttribute"/> of the specified assembly, or if those attributes are not 
		/// defined, the assembly version defined by <see cref="AssemblyVersionAttribute"/>. If this attribute is 
		/// not defined either, then null is returned.
		/// </summary>
		/// 
		/// <param name="assembly">
		/// The assembly for which to return the version.</param>
		/// 
		/// <returns>
		/// Either a string containing the version of the specified assembly, or null.</returns>
		/// 
		/// <exception cref="ArgumentNullException">
		/// A null reference was passed to a method that did not accept it as a valid argument.</exception>
		public static String GetVersion(Assembly assembly)
		{
			Verify.NotNull(assembly, "assembly");

			var informationalVersion = Helper.GetAssemblyInformationalVersion(assembly);

			if (informationalVersion != null)
				return informationalVersion;

			var fileVersion = Helper.GetAssemblyFileVersion(assembly);

			if (fileVersion != null)
				return fileVersion;

			return Helper.GetAssemblyVersion(assembly);
		}

		/// <summary>
		/// Returns the version of the Amarok Application Framework used by this application.
		/// </summary>
		/// 
		/// <returns>
		/// A string containing the version of the used Amarok Application Framework, or null if this 
		/// information could be obtained.</returns>
		public static String GetApplicationFrameworkVersion()
		{
			try
			{
				var frameworkAssembly = typeof(Helper).Assembly;
				return Helper.GetVersion(frameworkAssembly);
			}
			catch (Exception)
			{
				return null;
			}
		}

		/// <summary>
		/// Returns the version of the executable that starts this process.
		/// </summary>
		/// 
		/// <remarks>
		/// <para>
		/// Warning: Using this method in unit tests that are executed via TestDriven.NET might
		/// return unexpected results. In such a case the version of the TestDriven.NET executable
		/// "ProcessInvocation.exe" will be returned.</para>
		/// </remarks>
		/// 
		/// <returns>
		/// A string containing the version of the application, or null if this information could 
		/// be obtained.</returns>
		public static String GetApplicationVersion()
		{
			try
			{
				var applicationAssembly = Assembly.GetEntryAssembly();

				if (applicationAssembly == null)
					return null;

				return Helper.GetVersion(applicationAssembly);
			}
			catch (Exception)
			{
				return null;
			}
		}

		#endregion

		#region ++ Public Interface (GetAssemblyProduct, GetAssemblyCompany, GetAssemblyCopyright) ++

		/// <summary>
		/// Returns the product name defined by <see cref="AssemblyProductAttribute"/> of the specified assembly. 
		/// If the specified assembly does not define such attribute, then null is returned.
		/// </summary>
		/// 
		/// <param name="assembly">
		/// The assembly for which to return the product name.</param>
		/// 
		/// <returns>
		/// Either a string containing the product name, or null.</returns>
		/// 
		/// <exception cref="ArgumentNullException">
		/// A null reference was passed to a method that did not accept it as a valid argument.</exception>
		public static String GetAssemblyProduct(Assembly assembly)
		{
			Verify.NotNull(assembly, "assembly");

			var attribute = (AssemblyProductAttribute)Attribute.GetCustomAttribute(
				assembly,
				typeof(AssemblyProductAttribute));

			if (attribute == null)
				return null;

			return attribute.Product;
		}

		/// <summary>
		/// Returns the company name defined by <see cref="AssemblyCompanyAttribute"/> of the specified assembly. 
		/// If the specified assembly does not define such attribute, then null is returned.
		/// </summary>
		/// 
		/// <param name="assembly">
		/// The assembly for which to return the company name.</param>
		/// 
		/// <returns>
		/// Either a string containing the company name, or null.</returns>
		/// 
		/// <exception cref="ArgumentNullException">
		/// A null reference was passed to a method that did not accept it as a valid argument.</exception>
		public static String GetAssemblyCompany(Assembly assembly)
		{
			Verify.NotNull(assembly, "assembly");

			var attribute = (AssemblyCompanyAttribute)Attribute.GetCustomAttribute(
				assembly,
				typeof(AssemblyCompanyAttribute));

			if (attribute == null)
				return null;

			return attribute.Company;
		}

		/// <summary>
		/// Returns the copyright defined by <see cref="AssemblyCopyrightAttribute"/> of the specified assembly. 
		/// If the specified assembly does not define such attribute, then null is returned.
		/// </summary>
		/// 
		/// <param name="assembly">
		/// The assembly for which to return the copyright.</param>
		/// 
		/// <returns>
		/// Either a string containing the copyright, or null.</returns>
		/// 
		/// <exception cref="ArgumentNullException">
		/// A null reference was passed to a method that did not accept it as a valid argument.</exception>
		public static String GetAssemblyCopyright(Assembly assembly)
		{
			Verify.NotNull(assembly, "assembly");

			var attribute = (AssemblyCopyrightAttribute)Attribute.GetCustomAttribute(
				assembly,
				typeof(AssemblyCopyrightAttribute));

			if (attribute == null)
				return null;

			return attribute.Copyright;
		}

		#endregion

		#region ++ Public Interface (WaitForCondition) ++

		/// <summary>
		/// Waits until the supplied condition is satisfied.
		/// 
		/// The method blocks the calling thread until the supplied condition returns true. Between each condition 
		/// test the method sleeps the specified amount of time. After that time the thread wakes up and the 
		/// condition is evaluated once again.
		/// </summary>
		/// 
		/// <param name="sleepTime">
		/// The amount of time to sleep between two condition tests.</param>
		/// <param name="condition">
		/// The condition callback that is called for evaluation.</param>
		/// 
		/// <exception cref="ArgumentOutOfRangeException">
		/// A value was passed to a method that did not accept it as a valid argument, because the value must be 
		/// at least (equal to or greater than) the minimum value of zero.</exception>
		/// <exception cref="ArgumentNullException">
		/// A null reference was passed to a method that did not accept it as a valid argument.</exception>
		public static void WaitForCondition(TimeSpan sleepTime, Func<Boolean> condition)
		{
			Helper.WaitForCondition(
				(Int32)sleepTime.TotalMilliseconds,
				condition);
		}

		/// <summary>
		/// Waits until the supplied condition is satisfied.
		/// 
		/// The method blocks the calling thread until the supplied condition returns true. Between each condition 
		/// test the method sleeps the specified amount of time. After that time the thread wakes up and the 
		/// condition is evaluated once again.
		/// </summary>
		/// 
		/// <param name="sleepTime">
		/// The amount of time in milliseconds to sleep between two condition tests.</param>
		/// <param name="condition">
		/// The condition callback that is called for evaluation.</param>
		/// 
		/// <exception cref="ArgumentOutOfRangeException">
		/// A value was passed to a method that did not accept it as a valid argument, because the value must be 
		/// at least (equal to or greater than) the minimum value of zero.</exception>
		/// <exception cref="ArgumentNullException">
		/// A null reference was passed to a method that did not accept it as a valid argument.</exception>
		public static void WaitForCondition(Int32 sleepTime, Func<Boolean> condition)
		{
			Verify.NotNegative(sleepTime, "sleepTime");
			Verify.NotNull(condition, "condition");

			while (true)
			{
				if (condition())
					return;

				if (sleepTime >= 0)
					Thread.Sleep(sleepTime);
			}
		}

		/// <summary>
		/// Waits until the supplied condition is satisfied or the timeout is reached.
		/// 
		/// The method blocks the calling thread until the supplied condition returns true. Between each condition 
		/// test the method sleeps the specified amount of time. After that time the thread wakes up and the 
		/// condition is evaluated once again. Before sleeping once again, the elapsed time is compared to the 
		/// specified timeout. If the timeout is reached the method returned even though the condition was not 
		/// satisfied.
		/// </summary>
		/// 
		/// <param name="timeout">
		/// The maximum amount of time to wait for the condition to become satisfied.</param>
		/// <param name="sleepTime">
		/// The amount of time to sleep between two condition tests.</param>
		/// <param name="condition">
		/// The condition callback that is called for evaluation.</param>
		/// 
		/// <returns>
		/// True, if the condition was satisfied before reaching the timeout, otherwise False.</returns>
		/// 
		/// <exception cref="ArgumentOutOfRangeException">
		/// A value was passed to a method that did not accept it as a valid argument, because the value must be 
		/// at least (equal to or greater than) the minimum value of zero.</exception>
		/// <exception cref="ArgumentNullException">
		/// A null reference was passed to a method that did not accept it as a valid argument.</exception>
		public static Boolean WaitForCondition(TimeSpan timeout, TimeSpan sleepTime, Func<Boolean> condition)
		{
			return Helper.WaitForCondition(
				(Int32)timeout.TotalMilliseconds,
				(Int32)sleepTime.TotalMilliseconds,
				condition);
		}

		/// <summary>
		/// Waits until the supplied condition is satisfied or the timeout is reached.
		/// 
		/// The method blocks the calling thread until the supplied condition returns true. Between each condition 
		/// test the method sleeps the specified amount of time. After that time the thread wakes up and the 
		/// condition is evaluated once again. Before sleeping once again, the elapsed time is compared to the 
		/// specified timeout. If the timeout is reached the method returned even though the condition was not 
		/// satisfied.
		/// </summary>
		/// 
		/// <param name="timeout">
		/// The maximum amount of time in milliseconds to wait for the condition to become satisfied.</param>
		/// <param name="sleepTime">
		/// The amount of time in milliseconds to sleep between two condition tests.</param>
		/// <param name="condition">
		/// The condition callback that is called for evaluation.</param>
		/// 
		/// <returns>
		/// True, if the condition was satisfied before reaching the timeout, otherwise False.</returns>
		/// 
		/// <exception cref="ArgumentOutOfRangeException">
		/// A value was passed to a method that did not accept it as a valid argument, because the value must be 
		/// at least (equal to or greater than) the minimum value of zero.</exception>
		/// <exception cref="ArgumentNullException">
		/// A null reference was passed to a method that did not accept it as a valid argument.</exception>
		public static Boolean WaitForCondition(Int32 timeout, Int32 sleepTime, Func<Boolean> condition)
		{
			Verify.NotNegative(timeout, "timeout");
			Verify.NotNegative(sleepTime, "sleepTime");
			Verify.NotNull(condition, "condition");

			var watch = Stopwatch.StartNew();

			while (true)
			{
				if (condition())
					return true;

				if (watch.ElapsedMilliseconds > timeout)
					return false;

				if (sleepTime >= 0)
					Thread.Sleep(sleepTime);
			}
		}

		#endregion

		#region ++ Public Interface (WildcardMatch) ++

		/// <summary>
		/// Performs a wildcard match and returns a boolean value indicating whether the supplied input 
		/// matches the specified pattern.
		/// </summary>
		/// 
		/// <param name="input">
		/// The input string.</param>
		/// <param name="pattern">
		/// The pattern string, which may include wildcards (+, *, ?).</param>
		/// <param name="ignoreCase">
		/// A boolean value indicating whether casing should be ignored. Defaults to true.</param>
		/// 
		/// <returns>
		/// True, if the supplied input string matches the specified pattern; otherwise False.</returns>
		/// 
		/// <exception cref="ArgumentNullException">
		/// A null reference was passed to a method that did not accept it as a valid argument.</exception>
		public unsafe static Boolean WildcardMatch(String pattern, String input, Boolean ignoreCase = true)
		{
			Verify.NotNull(input, "input");
			Verify.NotNull(pattern, "pattern");

			// Alessandro Felice Cantatore
			// http://xoomer.virgilio.it/acantato/dev/wildcard/wildmatch.html
			//
			//
			//  BOOL szWildMatch7(PSZ pat, PSZ str) {
			//     PSZ s, p;
			//     BOOL star = FALSE;
			//
			//  loopStart:
			//     for (s = str, p = pat; *s; ++s, ++p) {
			//        switch (*p) {
			//           case '?':
			//              if (*s == '.') goto starCheck;
			//              break;
			//           case '*':
			//              star = TRUE;
			//              str = s, pat = p;
			//              do { ++pat; } while (*pat == '*');
			//              if (!*pat) return TRUE;
			//              goto loopStart;
			//           default:
			//              if (mapCaseTable[*s] != mapCaseTable[*p])
			//                 goto starCheck;
			//              break;
			//        } /* endswitch */
			//     } /* endfor */
			//     while (*p == '*') ++p;
			//     return (!*p);
			//
			//  starCheck:
			//     if (!star) return FALSE;
			//     str++;
			//     goto loopStart;
			//  }


			// BOOL szWildMatch7(PSZ pat, PSZ str) {
			//    PSZ s, p;
			//    BOOL star = FALSE;
			//
			fixed (Char* ptrPattern = pattern, ptrInput = input)
			{
				Char* pat = ptrPattern;
				Char* str = ptrInput;
				Char* s;
				Char* p;
				Boolean star = false;

			// loopStart:
			//    for (s = str, p = pat; *s; ++s, ++p) {
			//
			loopStart:
				for (s = str, p = pat; s < ptrInput + input.Length; ++s, ++p)
				{
					//   switch (*p) {
					//      case '?':
					//         if (*s == '.') goto starCheck;
					//         break;
					//
					if (*p == '?')
					{
						if (*s == '.')
							goto starCheck;
					}
					//      case '*':
					//         star = TRUE;
					//         str = s, pat = p;
					//         do { ++pat; } while (*pat == '*');
					//         if (!*pat) return TRUE;
					//         goto loopStart;
					//
					else if (*p == '*')
					{
						star = true;
						str = s;
						pat = p;

						do
						{
							++pat;
						} while (*pat == '*');

						if (pat >= ptrPattern + pattern.Length)
							return true;

						goto loopStart;
					}
					//      default:
					//         if (mapCaseTable[*s] != mapCaseTable[*p])
					//            goto starCheck;
					//         break;
					//
					else
					{
						if (ignoreCase)
						{
							if (Char.ToLowerInvariant(*s) != Char.ToLowerInvariant(*p))
								goto starCheck;
						}
						else
						{
							if (*s != *p)
								goto starCheck;
						}
					}
				}

				//     while (*p == '*') ++p;
				//     return (!*p);
				//
				while (*p == '*')
					++p;

				return p >= ptrPattern + pattern.Length;

			//  starCheck:
			//     if (!star) return FALSE;
			//     str++;
			//     goto loopStart;
			//
			starCheck:
				if (!star)
					return false;

				str++;
				goto loopStart;
			}
		}

		#endregion

		#region ++ Public Interface (GetTempPath, GetTempFile) ++

		/// <summary>
		/// Creates a new empty temporary folder path and returns a DirectoryInfo object to that folder. This method
		/// does not create the folder; it only generates a unique folder path.
		/// </summary>
		/// 
		/// <returns>
		/// A DirectoryInfo object representing the temporary folder.</returns>
		public static DirectoryInfo GetTempPath()
		{
			while (true)
			{
				var path = Path.Combine(
					Path.GetTempPath(),
					Guid.NewGuid().ToString("D").ToUpperInvariant());

				if (Directory.Exists(path))
					continue;

				return new DirectoryInfo(
					path);
			}
		}


		/// <summary>
		/// Creates a new unique temporary file path and returns a FileInfo object to that file. This method does 
		/// not create the file; it only generates a unique file path.
		/// </summary>
		/// 
		/// <returns>
		/// A FileInfo object representing the temporary file.</returns>
		public static FileInfo GetTempFile()
		{
			while (true)
			{
				var path = Path.Combine(
					Path.GetTempPath(),
					Guid.NewGuid().ToString("D").ToLowerInvariant() + ".tmp");

				if (File.Exists(path))
					continue;

				return new FileInfo(
					path);
			}
		}

		#endregion

	}
}
